package forward

import (
	"context"
	"gitee.com/sunny-zhangqiang/zcache/comm"
	"gitee.com/sunny-zhangqiang/zcache/comm/logger"
	"gitee.com/sunny-zhangqiang/zcache/config"
	"gitee.com/sunny-zhangqiang/zcache/xhttp"
	"net"
	"syscall"
	"time"
)

const (
	transportNum            = 16
	IP_BIND_ADDRESS_NO_PORT = 0x18
)

var (
	mgr    *transportMgr
	dialer *customDialer
)

type transportMgr struct {
	transports [transportNum]*xhttp.Transport
}

type customDialer struct {
	sc     *config.Server
	dialer *net.Dialer
}

// set sock options
func (cd *customDialer) control(network string, address string, c syscall.RawConn) error {
	var fn func(uintptr)
	fn = func(s uintptr) {
		var err error

		if cd.sc.ClientConnTCPRecvBufKB > 0 {
			recvBufSize := cd.sc.ClientConnTCPRecvBufKB << 10
			err = syscall.SetsockoptInt(int(s), syscall.SOL_SOCKET, syscall.SO_RCVBUF, recvBufSize)
			if err != nil {
				logger.Error("set socket(SO_RCVBUF) failed, err: %v", err)
			}
		}

		// 当绑定LocalAddr发起connect操作时, 需要设置该标记.,
		err = syscall.SetsockoptInt(int(s), syscall.IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, 1)
		if err != nil {
			logger.Error("set socket(IP_BIND_ADDRESS_NO_PORT) failed, err: %v", err)
		}

		err = syscall.SetsockoptLinger(int(s), syscall.SOL_SOCKET, syscall.SO_LINGER, &syscall.Linger{1, 0})
		if err != nil {
			logger.Error("set socket(SO_LINGER) failed, err: %v", err)
		}
	}

	return c.Control(fn)
}

func (d *customDialer) customDialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
	return d.dialer.DialContext(ctx, network, addr)
}

func newCustomDialer(serverConf *config.Server) *customDialer {
	cd := &customDialer{
		sc: serverConf,
		dialer: &net.Dialer{
			Timeout:   time.Millisecond * time.Duration(serverConf.ClientConnTimeoutMsec),
			KeepAlive: 60 * time.Second,
			//LocalAddr:
		},
	}

	cd.dialer.Control = cd.control
	return cd
}

func InitTransport(serverConf *config.Server) {
	tpMgr := &transportMgr{}
	cd := newCustomDialer(serverConf)

	for i := 0; i < transportNum; i++ {
		tpMgr.transports[i] = &xhttp.Transport{
			DialContext:           cd.customDialContext,
			MaxIdleConns:          serverConf.MaxClientIdleConns,
			MaxIdleConnsPerHost:   serverConf.MaxClientIdleConnsPerHost,
			IdleConnTimeout:       time.Millisecond * time.Duration(serverConf.ClientConnIdleTimeoutMsec),
			TLSHandshakeTimeout:   time.Millisecond * time.Duration(serverConf.TLSHandshakeTimeoutMsec),
			ResponseHeaderTimeout: time.Millisecond * time.Duration(serverConf.ClientResponseHeaderTimeoutMsec),
			ReadDataTimeout:       time.Millisecond * time.Duration(serverConf.ClientReadDataTimeoutMsec),
			WriteDataTimeout:      time.Millisecond * time.Duration(serverConf.ClientWriteDataTimeoutMsec),
			DisableCompression:    true,
		}
	}

	mgr = tpMgr
	dialer = cd
}

func PickTransport(address string) *xhttp.Transport {
	if mgr == nil {
		panic("transport mgr no inited")
	}

	hash := comm.FnvHash(comm.String2Bytes(address))
	return mgr.transports[hash%transportNum]
}
